/*
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: ddcalls.c
 *
 *  Manages DirectDraw objects needed for rendering.  Part of D3DApp.
 *
 *  D3DApp is a collection of helper functions for Direct3D applications.
 *  D3DApp consists of the following files:
 *      d3dapp.h    Main D3DApp header to be included by application
 *      d3dappi.h   Internal header
 *      d3dapp.c    D3DApp functions seen by application.
 *      ddcalls.c   All calls to DirectDraw objects except textures
 *      d3dcalls.c  All calls to Direct3D objects except textures
 *      texture.c   Texture loading and managing texture list
 *      misc.c      Miscellaneous calls
 */

#include "d3dappi.h"

/***************************************************************************/
/*                         Direct Draw Creation                            */
/***************************************************************************/
/*
 * D3DAppIDDEnumCallback
 * Callback function used during enumeration of DirectDraw drivers.
 * During enumeration, if a 3D capable hardware device is found, it is 
 * created and *(LPDIRECTDRAW*)lpContext is set to it.  Otherwise, does
 * nothing.
 */
BOOL FAR PASCAL D3DAppIDDEnumCallback(GUID FAR* lpGUID, LPSTR lpDriverDesc,
                                      LPSTR lpDriverName, LPVOID lpContext)
{
    LPDIRECTDRAW lpDD;
    DDCAPS DriverCaps, HELCaps;

    /*
     * A NULL GUID* indicates the DirectDraw HEL which we are not interested
     * in at the moment.
     */
    if (lpGUID) {
        /*
         * Create the DirectDraw device using this driver.  If it fails,
         * just move on to the next driver.
         */
        if (FAILED(DirectDrawCreate(lpGUID, &lpDD, NULL))) {
            return DDENUMRET_OK;
        }
        /*
         * Get the capabilities of this DirectDraw driver.  If it fails,
         * just move on to the next driver.
         */
        memset(&DriverCaps, 0, sizeof(DDCAPS));
        DriverCaps.dwSize = sizeof(DDCAPS);
        memset(&HELCaps, 0, sizeof(DDCAPS));
        HELCaps.dwSize = sizeof(DDCAPS);
        if (FAILED(lpDD->lpVtbl->GetCaps(lpDD, &DriverCaps, &HELCaps))) {
            lpDD->lpVtbl->Release(lpDD);
            return DDENUMRET_OK;
        }
        if (DriverCaps.dwCaps & DDCAPS_3D) {
            /*
             * We have found a 3d hardware device.  Return the DD object
             * and stop enumeration.
             */
            d3dappi.bIsPrimary = FALSE;
            *(LPDIRECTDRAW*)lpContext = lpDD;
            return DDENUMRET_CANCEL;
        }       
        lpDD->lpVtbl->Release(lpDD);
    }
    return DDENUMRET_OK;
}

/*
 * D3DAppICreateDD
 * Creates the DirectDraw device and saves the current palette. If a 3D 
 * capable DD driver is available, use it as the DD device, otherwise, use
 * the HEL.  It is assumed that a 3D capable DD hardware driver is not the
 * primary device and hence cannot operate in a window (ie it's a fullscreen
 * only device displaying on a second monitor).  Valid flags:
 *     D3DAPP_ONLYDDEMULATION    Always use the DirectDraw HEL
 */
BOOL
D3DAppICreateDD(DWORD flags)
{
    HDC hdc;
    int i;
    LPDIRECTDRAW lpDD = NULL;

    /*
     * If we aren't forced to use the DirectDraw HEL, search for a 3D capable
     * DirectDraw hardware driver and create it.
     */
    if (!(flags & D3DAPP_ONLYDDEMULATION)) {
        LastError = DirectDrawEnumerate(D3DAppIDDEnumCallback, &lpDD);
        if (LastError != DD_OK) {
            D3DAppISetErrorString("DirectDrawEnumerate failed.\n%s",
                                  D3DAppErrorToString(LastError));
            return FALSE;
        }
    }
    if (!lpDD) {
        /*
         * If we haven't created a hardware DD device by now, resort to HEL
         */
        d3dappi.bIsPrimary = TRUE;
        LastError = DirectDrawCreate(NULL, &d3dappi.lpDD, NULL);
        if (LastError != DD_OK) {
            D3DAppISetErrorString("DirectDrawCreate failed.\n%s",
                                  D3DAppErrorToString(LastError));
            return FALSE;
        }
    } else {
        d3dappi.lpDD = lpDD;
    }
    /*
     * Save the original palette for when we are paused.  Just in case we
     * start in a fullscreen mode, put them in ppe.
     */
    hdc = GetDC(NULL);
    GetSystemPaletteEntries(hdc, 0, (1 << 8),
                            (LPPALETTEENTRY)(&Originalppe[0]));
    for (i = 0; i < 256; i++)
        ppe[i] = Originalppe[i];
    ReleaseDC(NULL, hdc);
    return TRUE;
}

/***************************************************************************/
/*                   Enumerating the display modes                         */
/***************************************************************************/
/*
 * EnumDisplayModesCallback
 * Callback to save the display mode information.
 */
static HRESULT
CALLBACK EnumDisplayModesCallback(LPDDSURFACEDESC pddsd, LPVOID lpContext)
{
    /*
     * Very large resolutions cause problems on some hardware.  They are also
     * not very useful for real-time rendering.  We have chosen to disable
     * them by not reporting them as available.
     */
    if (pddsd->dwWidth > 1024 || pddsd->dwHeight > 768)
        return DDENUMRET_OK;
    /*
     * Save this mode at the end of the mode array and increment mode count
     */
    d3dappi.Mode[d3dappi.NumModes].w = pddsd->dwWidth;
    d3dappi.Mode[d3dappi.NumModes].h = pddsd->dwHeight;
    d3dappi.Mode[d3dappi.NumModes].bpp = pddsd->ddpfPixelFormat.dwRGBBitCount;
    d3dappi.Mode[d3dappi.NumModes].bThisDriverCanDo = FALSE;
    d3dappi.NumModes++;
    if (d3dappi.NumModes == D3DAPP_MAXMODES)
        return DDENUMRET_CANCEL;
    else
        return DDENUMRET_OK;
}

/*
 * CompareModes
 * Compare two display modes during sorting.  Modes are sorted by depth and
 * then resolution.
 */
static int
_cdecl CompareModes(const void* element1, const void* element2) {
    D3DAppMode *lpMode1, *lpMode2;

    lpMode1 = (D3DAppMode*)element1;
    lpMode2 = (D3DAppMode*)element2;

    if (lpMode1->bpp > lpMode2->bpp)
        return -1;
    else if (lpMode2->bpp > lpMode1->bpp)
        return 1;
    else if (lpMode1->w > lpMode2->w)
        return -1;
    else if (lpMode2->w > lpMode1->w)
        return 1;
    else if (lpMode1->h > lpMode2->h)
        return -1;
    else if (lpMode2->h > lpMode1->h)
        return 1;
    else
        return 0;
}

/*
 * EnumerateDisplayModes
 * Generates the list of available display modes.
 */
BOOL
D3DAppIEnumDisplayModes(void)
{
    int i;
    /*
     * Get a list of available display modes from DirectDraw
     */
    d3dappi.NumModes = 0;
    LastError = d3dappi.lpDD->lpVtbl->EnumDisplayModes(d3dappi.lpDD, 0, NULL,
                                                0, EnumDisplayModesCallback);
    if(LastError != DD_OK ) {
        D3DAppISetErrorString("EnumDisplayModes failed.\n%s",
                              D3DAppErrorToString(LastError));
        d3dappi.NumModes = 0;
        return FALSE;
    }
    /*
     * Sort the list of display modes
     */
    qsort((void *)&d3dappi.Mode[0], (size_t)d3dappi.NumModes, sizeof(D3DAppMode),
          CompareModes);
    /*
     * Pick a default display mode.  640x480x16 is a very good mode for
     * rendering, so choose it over all others.  Otherwise, just take the
     * first one.  This selection may be overriden later if a driver is
     * created which cannot render in this mode.
     */
    d3dappi.CurrMode = 0;
    for (i = 0; i < d3dappi.NumModes; i++) {
        if (d3dappi.Mode[i].w == 640 && d3dappi.Mode[i].h == 480 &&
            d3dappi.Mode[i].bpp == 16)
            d3dappi.CurrMode = i;
    }
    memcpy(&d3dappi.ThisMode, &d3dappi.Mode[d3dappi.CurrMode],
           sizeof(D3DAppMode));
    return TRUE;
}

/***************************************************************************/
/*               Creating Front and Back Buffers (and misc surf funcs)     */
/***************************************************************************/
/*
 * D3DAppICreateSurface
 * Create a DirectDraw Surface of the given description.  Using this function
 * ensures that all surfaces end up in system memory if that option was set.
 * Returns the result of the CreateSurface call.
 */
HRESULT
D3DAppICreateSurface(LPDDSURFACEDESC lpDDSurfDesc,
                LPDIRECTDRAWSURFACE FAR *lpDDSurface) {
    HRESULT result;
    if (d3dappi.bOnlySystemMemory)
        lpDDSurfDesc->ddsCaps.dwCaps |= DDSCAPS_SYSTEMMEMORY;
    result = d3dappi.lpDD->lpVtbl->CreateSurface(d3dappi.lpDD, lpDDSurfDesc,
                                                 lpDDSurface, NULL);
    return result;
}

/*
 * D3DAppIGetSurfDesc
 * Get the description of the given surface.  Returns the result of the
 * GetSurfaceDesc call.
 */
HRESULT
D3DAppIGetSurfDesc(LPDDSURFACEDESC lpDDSurfDesc,LPDIRECTDRAWSURFACE lpDDSurf)
{
    HRESULT result;
    memset(lpDDSurfDesc, 0, sizeof(DDSURFACEDESC));
    lpDDSurfDesc->dwSize = sizeof(DDSURFACEDESC);
    result = lpDDSurf->lpVtbl->GetSurfaceDesc(lpDDSurf, lpDDSurfDesc);
    return result;
}

/*
 * D3DAppICreateBuffers
 * Creates the front and back buffers for the window or fullscreen case
 * depending on the bFullscreen flag.  In the window case, bpp is ignored.
 */
BOOL
D3DAppICreateBuffers(HWND hwnd, int w, int h, int bpp, BOOL bFullscreen, BOOL bIsHardware)
{
    DDSURFACEDESC ddsd;
    DDSCAPS ddscaps;

    /*
     * Release any old objects that might be lying around.  This should have
     * already been taken care of, but just in case...
     */
    RELEASE(lpClipper);
    RELEASE(d3dappi.lpBackBuffer);
    RELEASE(d3dappi.lpFrontBuffer);
    /*
     * The size of the buffers is going to be w x h, so record it now
     */
    if (w < D3DAPP_WINDOWMINIMUM)
        w = D3DAPP_WINDOWMINIMUM;
    if (h < D3DAPP_WINDOWMINIMUM)
        h = D3DAPP_WINDOWMINIMUM;
    szBuffers.cx = w;
    szBuffers.cy = h;

    if (bFullscreen) {
        /*
         * Create a complex flipping surface for fullscreen mode with one
         * back buffer.
         */
        memset(&ddsd,0,sizeof(DDSURFACEDESC));
        ddsd.dwSize = sizeof( ddsd );
        ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
        ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP |
            DDSCAPS_3DDEVICE | DDSCAPS_COMPLEX;
        ddsd.dwBackBufferCount = 1;
        if (bIsHardware)
            ddsd.ddsCaps.dwCaps |= DDSCAPS_VIDEOMEMORY;
        LastError = D3DAppICreateSurface(&ddsd, &d3dappi.lpFrontBuffer);
        if(LastError != DD_OK) {
            if (LastError == DDERR_OUTOFMEMORY || LastError == DDERR_OUTOFVIDEOMEMORY) {
                D3DAppISetErrorString("There was not enough video memory to create the rendering surface.\nPlease restart the program and try another fullscreen mode with less resolution or lower bit depth.");
            } else {
                D3DAppISetErrorString("CreateSurface for fullscreen flipping surface failed.\n%s",
                                      D3DAppErrorToString(LastError));
            }
            goto exit_with_error;
        }
        /* 
         * Obtain a pointer to the back buffer surface created above so we
         * can use it later.  For now, just check to see if it ended up in
         * video memory (FYI).
         */
        ddscaps.dwCaps = DDSCAPS_BACKBUFFER;
        LastError = d3dappi.lpFrontBuffer->lpVtbl->GetAttachedSurface(d3dappi.lpFrontBuffer, &ddscaps, &d3dappi.lpBackBuffer);
        if(LastError != DD_OK) {
            D3DAppISetErrorString("GetAttachedSurface failed to get back buffer.\n%s",
                                  D3DAppErrorToString(LastError));
            goto exit_with_error;
        }
        LastError = D3DAppIGetSurfDesc(&ddsd, d3dappi.lpBackBuffer);
        if (LastError != DD_OK) {
            D3DAppISetErrorString("Failed to get surface description of back buffer.\n%s",
                                  D3DAppErrorToString(LastError));
            goto exit_with_error;
        }
        d3dappi.bBackBufferInVideo =
                  (ddsd.ddsCaps.dwCaps & DDSCAPS_VIDEOMEMORY) ? TRUE : FALSE;
    }
    else {
        /*
         * In the window case, create a front buffer which is the primary
         * surface and a back buffer which is an offscreen plane surface.
         */
        memset(&ddsd,0,sizeof(DDSURFACEDESC));
        ddsd.dwSize = sizeof(DDSURFACEDESC);
        ddsd.dwFlags = DDSD_CAPS;
        ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
        /*
         * If we specify system memory when creating a primary surface, we
         * won't get the actual primary surface in video memory.  So, don't
         * use D3DAppICreateSurface().
         */
        LastError = d3dappi.lpDD->lpVtbl->CreateSurface(d3dappi.lpDD,
                                        &ddsd, &d3dappi.lpFrontBuffer, NULL);
        if(LastError != DD_OK ) {
            if (LastError == DDERR_OUTOFMEMORY || LastError == DDERR_OUTOFVIDEOMEMORY) {
                D3DAppISetErrorString("There was not enough video memory to create the rendering surface.\nTo run this program in a window of this size, please adjust your display settings for a smaller desktop area or a lower palette size and restart the program.");
            } else {
                D3DAppISetErrorString("CreateSurface for window front buffer failed.\n%s",
                                      D3DAppErrorToString(LastError));
            }
            goto exit_with_error;
        }
        ddsd.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS;
        ddsd.dwWidth = w;
        ddsd.dwHeight = h;
        ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_3DDEVICE;
        if (bIsHardware)
            ddsd.ddsCaps.dwCaps |= DDSCAPS_VIDEOMEMORY;
        else
            ddsd.ddsCaps.dwCaps |= DDSCAPS_SYSTEMMEMORY;
        LastError = D3DAppICreateSurface(&ddsd, &d3dappi.lpBackBuffer);
        if (LastError != DD_OK) {
            if (LastError == DDERR_OUTOFMEMORY || LastError == DDERR_OUTOFVIDEOMEMORY) {
                D3DAppISetErrorString("There was not enough video memory to create the rendering surface.\nTo run this program in a window of this size, please adjust your display settings for a smaller desktop area or a lower palette size and restart the program.");
            } else {
                D3DAppISetErrorString("CreateSurface for window back buffer failed.\n%s",
                                      D3DAppErrorToString(LastError));
            }
            goto exit_with_error;
        }
        /*
         * Check to see if the back buffer is in video memory (FYI).
         */
        LastError = D3DAppIGetSurfDesc(&ddsd, d3dappi.lpBackBuffer);
        if (LastError != DD_OK) {
            D3DAppISetErrorString("Failed to get surface description for back buffer.\n%s",
                                  D3DAppErrorToString(LastError));
            goto exit_with_error;
        }
        d3dappi.bBackBufferInVideo =
                  (ddsd.ddsCaps.dwCaps & DDSCAPS_VIDEOMEMORY) ? TRUE : FALSE;
        /*
         * Create the DirectDraw Clipper object and attach it to the window
         * and front buffer.
         */
        LastError = d3dappi.lpDD->lpVtbl->CreateClipper(d3dappi.lpDD, 0,
                                                        &lpClipper, NULL);
        if(LastError != DD_OK ) {
            D3DAppISetErrorString("CreateClipper failed.\n%s",
                                  D3DAppErrorToString(LastError));
            goto exit_with_error;
        }
        LastError = lpClipper->lpVtbl->SetHWnd(lpClipper, 0, hwnd);
        if(LastError != DD_OK ) {
            D3DAppISetErrorString("Attaching clipper to window failed.\n%s",
                                  D3DAppErrorToString(LastError));
            goto exit_with_error;
        }
        LastError =
             d3dappi.lpFrontBuffer->lpVtbl->SetClipper(d3dappi.lpFrontBuffer,
                                                       lpClipper);
        if(LastError != DD_OK ) {
            D3DAppISetErrorString("Attaching clipper to front buffer failed.\n%s",
                                  D3DAppErrorToString(LastError));
            goto exit_with_error;
        }
    }

    D3DAppIClearBuffers();
    return TRUE;

exit_with_error:
    RELEASE(d3dappi.lpFrontBuffer);
    RELEASE(d3dappi.lpBackBuffer);
    RELEASE(lpClipper);
    return FALSE;
}

/*
 * D3DAppICheckForPalettized
 * If the front/back buffer is palettized, we need to create a palette.
 */
BOOL
D3DAppICheckForPalettized(void)
{
    DDSURFACEDESC ddsd;
    /*
     * Get the back buffer surface description and check to see if it's
     * palettized
     */
    LastError = D3DAppIGetSurfDesc(&ddsd, d3dappi.lpBackBuffer);
    if (LastError != DD_OK) {
        D3DAppISetErrorString("Failed to get surface description for back buffer for palettizing.\n%s",
                              D3DAppErrorToString(LastError));
        goto exit_with_error;
    }
    bPrimaryPalettized = 
        (ddsd.ddpfPixelFormat.dwFlags & DDPF_PALETTEINDEXED8) ? TRUE : FALSE;

    if (bPrimaryPalettized) {
        int i;
        /*
         * Get the current palette.
         */
        HDC hdc = GetDC(NULL);
        GetSystemPaletteEntries(hdc, 0, (1 << 8), ppe);
        ReleaseDC(NULL, hdc);
        /*
         * Change the flags on the palette entries to allow D3D to change
         * some of them.  In the window case, we must not change the top and
         * bottom ten (system colors), but in a fullscreen mode we can have
         * all but the first and last.
         */
        if (!d3dappi.bFullscreen) {
            for (i = 0; i < 10; i++) ppe[i].peFlags = D3DPAL_READONLY;
            for (i = 10; i < 256 - 10; i++) ppe[i].peFlags = D3DPAL_FREE | PC_RESERVED;
            for (i = 256 - 10; i < 256; i++) ppe[i].peFlags = D3DPAL_READONLY;
        } else {
            ppe[0].peFlags = D3DPAL_READONLY;
            for (i = 1; i < 255; i++) ppe[i].peFlags = D3DPAL_FREE | PC_RESERVED;
            ppe[255].peFlags = D3DPAL_READONLY;
        }
        /*
         * Create a palette using the old colors and new flags
         */
        LastError = d3dappi.lpDD->lpVtbl->CreatePalette(d3dappi.lpDD,
                                           DDPCAPS_8BIT | DDPCAPS_INITIALIZE,
                                           ppe, &lpPalette, NULL);
        if (LastError != DD_OK) {
            D3DAppISetErrorString("CreatePalette failed.\n%s",
                                  D3DAppErrorToString(LastError));
            goto exit_with_error;
        }
        /*
         * Set this as the front and back buffers' palette
         */
        LastError =
               d3dappi.lpBackBuffer->lpVtbl->SetPalette(d3dappi.lpBackBuffer,
                                                        lpPalette);
        if(LastError != DD_OK ) {
            D3DAppISetErrorString("SetPalette failed on back buffer.\n%s",
                                  D3DAppErrorToString(LastError));
            goto exit_with_error;
        }
        LastError =
             d3dappi.lpFrontBuffer->lpVtbl->SetPalette(d3dappi.lpFrontBuffer,
                                                       lpPalette);
        if(LastError != DD_OK ) {
            D3DAppISetErrorString("SetPalette failed on front buffer.\n%s",
                                  D3DAppErrorToString(LastError));
            goto exit_with_error;
        }
        /*
         * The palette is now valid, so set it again on anyt WM_ACTIVATE
         */
        bPaletteActivate = TRUE;
    }
    return TRUE;
exit_with_error:
    RELEASE(lpPalette);
    return FALSE;
}

/***************************************************************************/
/*                           Creation of Z-Buffer                          */
/***************************************************************************/
/*
 * D3DAppICreateZBuffer
 * Create a Z-Buffer of the appropriate depth and attach it to the back
 * buffer.
 */
BOOL
D3DAppICreateZBuffer(int w, int h, int driver)
{
    DDSURFACEDESC ddsd;
    DWORD devDepth;
    /*
     * Release any Z-Buffer that might be around just in case.
     */
    RELEASE(d3dappi.lpZBuffer);
    
    /*
     * If this driver does not do z-buffering, don't create a z-buffer
     */
    if (!d3dappi.Driver[driver].bDoesZBuffer)
        return TRUE;

    memset(&ddsd, 0 ,sizeof(DDSURFACEDESC));
    ddsd.dwSize = sizeof( ddsd );
    ddsd.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS |
                   DDSD_ZBUFFERBITDEPTH;
    ddsd.ddsCaps.dwCaps = DDSCAPS_ZBUFFER;
    ddsd.dwHeight = h;
    ddsd.dwWidth = w;
    /*
     * If this is a hardware D3D driver, the Z-Buffer MUST end up in video
     * memory.  Otherwise, it MUST end up in system memory.
     */
    if (d3dappi.Driver[driver].bIsHardware)
        ddsd.ddsCaps.dwCaps |= DDSCAPS_VIDEOMEMORY;
    else
        ddsd.ddsCaps.dwCaps |= DDSCAPS_SYSTEMMEMORY;
    /*
     * Get the Z buffer bit depth from this driver's D3D device description
     */
    devDepth = d3dappi.Driver[driver].Desc.dwDeviceZBufferBitDepth;
    if (devDepth & DDBD_32)
        ddsd.dwZBufferBitDepth = 32;
    else if (devDepth & DDBD_24)
        ddsd.dwZBufferBitDepth = 24;
    else if (devDepth & DDBD_16)
        ddsd.dwZBufferBitDepth = 16;
    else if (devDepth & DDBD_8)
        ddsd.dwZBufferBitDepth = 8;
    else {
        D3DAppISetErrorString("Unsupported Z-buffer depth requested by device.\n");
        return FALSE;
    }
    LastError = d3dappi.lpDD->lpVtbl->CreateSurface(d3dappi.lpDD, &ddsd,
                                                    &d3dappi.lpZBuffer,
                                                    NULL);
    if(LastError != DD_OK) {
        if (LastError == DDERR_OUTOFMEMORY || LastError == DDERR_OUTOFVIDEOMEMORY) {
            if (d3dappi.bFullscreen) {
                D3DAppISetErrorString("There was not enough video memory to create the Z-buffer surface.\nPlease restart the program and try another fullscreen mode with less resolution or lower bit depth.");
            } else {
                D3DAppISetErrorString("There was not enough video memory to create the Z-buffer surface.\nTo run this program in a window of this size, please adjust your display settings for a smaller desktop area or a lower palette size and restart the program.");
            }
        } else {
            D3DAppISetErrorString("CreateSurface for Z-buffer failed.\n%s",
                                  D3DAppErrorToString(LastError));
        }
        goto exit_with_error;
    }
    /*
     * Attach the Z-buffer to the back buffer so D3D will find it
     */
    LastError =
       d3dappi.lpBackBuffer->lpVtbl->AddAttachedSurface(d3dappi.lpBackBuffer,
                                                        d3dappi.lpZBuffer);
    if(LastError != DD_OK) {
        D3DAppISetErrorString("AddAttachedBuffer failed for Z-Buffer.\n%s",
                              D3DAppErrorToString(LastError));
        goto exit_with_error;
    }
    /*
     * Find out if it ended up in video memory.
     */
    LastError = D3DAppIGetSurfDesc(&ddsd, d3dappi.lpZBuffer);
    if (LastError != DD_OK) {
        D3DAppISetErrorString("Failed to get surface description of Z buffer.\n%s",
                              D3DAppErrorToString(LastError));
        goto exit_with_error;
    }
    d3dappi.bZBufferInVideo =
                  (ddsd.ddsCaps.dwCaps & DDSCAPS_VIDEOMEMORY) ? TRUE : FALSE;
    if (d3dappi.Driver[driver].bIsHardware && !d3dappi.bZBufferInVideo) {
        D3DAppISetErrorString("Could not fit the Z-buffer in video memory for this hardware device.\n");
        goto exit_with_error;
    }

    return TRUE;

exit_with_error:
    RELEASE(d3dappi.lpZBuffer);
    return FALSE;
}

/***************************************************************************/
/*                             WM_SIZE Handler                             */
/***************************************************************************/
/*
 * D3DAppIHandleWM_SIZE
 * Processes the WM_SIZE message.  Resizes all the buffers and re-creates
 * device if necessary.
 */
BOOL
D3DAppIHandleWM_SIZE(LRESULT* lresult, HWND hwnd, UINT message,
                     WPARAM wParam, LPARAM lParam)
{
    int w, h, i;
    /*
     * If we have minimzied, take note and call the default window proc
     */
    if (wParam == SIZE_MINIMIZED) {
        d3dappi.bMinimized = TRUE;
        *lresult = DefWindowProc(hwnd, message, wParam, lParam);
        return TRUE;
    }
    /*
     * In fullscreen mode, restore our surfaces and let DDraw take
     * care of the rest.
     */
    if (d3dappi.bFullscreen) {
        D3DAppIValidateDirtyRects();
        D3DAppCheckForLostSurfaces();
        d3dappi.bMinimized = FALSE;
        *lresult = DefWindowProc(hwnd, message, wParam, lParam);
        return TRUE;
    }
    /*
     * If we are minimized, this is the un-minimized size message.
     */
    if (d3dappi.bMinimized) {
        /*
         * Restore our surfaces and update the dirty rectangle info
         */
        D3DAppIValidateDirtyRects();
        D3DAppCheckForLostSurfaces();
        d3dappi.bMinimized = FALSE;
        *lresult = DefWindowProc(hwnd, message, wParam, lParam);
        return TRUE;
    }
    /*
     * Since we are still here, this must be a regular, window resize
     * message.  A new viewport will definitely be needed, but the
     * device and buffers will only be re-created if they have gotten bigger
     * or change size by a very large amount.
     */
    D3DAppIGetClientWin(hwnd);
    w = LOWORD(lParam);
    h = HIWORD(lParam);
    /*
     * If w and h are under the minimum, create buffers of the minimum size
     */
    if (w < D3DAPP_WINDOWMINIMUM)
        w = D3DAPP_WINDOWMINIMUM;
    if (h < D3DAPP_WINDOWMINIMUM)
        h = D3DAPP_WINDOWMINIMUM;
    /*
     * Destroy the viewport and all execute buffers
     */
    d3dappi.bRenderingIsOK = FALSE;
    ATTEMPT(D3DAppICallDeviceDestroyCallback());
    /*
     * Only create a new device and buffers if they changed significantly,
     * otherwise just make sure the old buffers aren't lost.
     */
    if ((w > szBuffers.cx || h > szBuffers.cy) ||
        (w < szBuffers.cx / 2 || h < szBuffers.cy / 2)) {
        /*
         * Release the device
         */
        RELEASE(d3dappi.lpD3DDevice);
        /*
         * Release the old buffers
         */
        RELEASE(d3dappi.lpZBuffer);
        RELEASE(lpPalette);
        RELEASE(lpClipper);
        RELEASE(d3dappi.lpBackBuffer);
        RELEASE(d3dappi.lpFrontBuffer);
        /*
         * Create new ones
         */
        ATTEMPT(D3DAppICreateBuffers(hwnd, w, h, D3DAPP_BOGUS, FALSE, d3dappi.ThisDriver.bIsHardware));
        ATTEMPT(D3DAppICheckForPalettized());
        ATTEMPT(D3DAppICreateZBuffer(w, h, d3dappi.CurrDriver));
        /*
         * Create the driver
         */
        ATTEMPT(D3DAppICreateDevice(d3dappi.CurrDriver));
        /*
         * Since the driver did not change, the texture surfaces are still valid.
         * We just need to get new handles.
         */
        if (d3dappi.ThisDriver.bDoesTextures) {
            for (i = 0; i < d3dappi.NumUsableTextures; i++) {
                D3DAppIGetTextureHandle(i);
            }
        }
    } else {
        D3DAppCheckForLostSurfaces();
    }
    /*
     * Call the device create callback to create the viewport, set the render
     * state and clear the dirty rectangle info
     */
    ATTEMPT(D3DAppICallDeviceCreateCallback(w, h));
    ATTEMPT(D3DAppISetRenderState());
    D3DAppIValidateDirtyRects();
    d3dappi.bRenderingIsOK = TRUE;
    /*
     * Call the default window proc
     */
    *lresult = DefWindowProc(hwnd, message, wParam, lParam);
    return TRUE;
exit_with_error:
    D3DAppICallDeviceDestroyCallback();
    RELEASE(d3dappi.lpD3DDevice);
    RELEASE(d3dappi.lpZBuffer);
    RELEASE(lpPalette);
    RELEASE(lpClipper);
    RELEASE(d3dappi.lpBackBuffer);
    RELEASE(d3dappi.lpFrontBuffer);
    return FALSE;
}

/***************************************************************************/
/*              Setting the display mode and cooperative level             */
/***************************************************************************/
/*
 * D3DAppISetCoopLevel
 * Set the cooperative level to exclusive mode for fullscreen and normal for
 * a window.  Set the bIgnoreWM_SIZE flag because SetCooperativeLevel
 * generates a WM_SIZE message you do not have to resize the buffers on.
 */
BOOL
D3DAppISetCoopLevel(HWND hwnd, BOOL bFullscreen)
{
    if (bFullscreen) {
        bIgnoreWM_SIZE = TRUE;
        LastError = d3dappi.lpDD->lpVtbl->SetCooperativeLevel(d3dappi.lpDD,
                                   hwnd, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN);
        bIgnoreWM_SIZE = FALSE;
        if(LastError != DD_OK ) {
            D3DAppISetErrorString("SetCooperativeLevel to fullscreen failed.\n%s",
                                  D3DAppErrorToString(LastError));
            return FALSE;
        }
        d3dappi.bFullscreen = TRUE;
    } else {
        bIgnoreWM_SIZE = TRUE;
        LastError = d3dappi.lpDD->lpVtbl->SetCooperativeLevel(d3dappi.lpDD,
                                                         hwnd, DDSCL_NORMAL);
        bIgnoreWM_SIZE = FALSE;
        if(LastError != DD_OK ) {
            D3DAppISetErrorString("SetCooperativeLevel to normal failed.\n%s",
                                  D3DAppErrorToString(LastError));
            return FALSE;
        }
        d3dappi.bFullscreen = FALSE;
    }
    return TRUE;
}

/*
 * D3DAppISetDisplayMode
 * Set the display mode to the given dimensions and bits per pixel.  The
 * bIgnoreWM_SIZE message is set because the display change generates a
 * WM_SIZE message which we don't want to resize the buffers on.
 */
BOOL
D3DAppISetDisplayMode(int w, int h, int bpp)
{
    d3dappi.ThisMode.w = w; d3dappi.ThisMode.h = h;
    d3dappi.ThisMode.bpp = bpp;
    bIgnoreWM_SIZE = TRUE;
    LastError = d3dappi.lpDD->lpVtbl->SetDisplayMode(d3dappi.lpDD, w, h,
                                                     bpp);
    bIgnoreWM_SIZE = FALSE;
    if(LastError != DD_OK ) {
        D3DAppISetErrorString("SetDisplayMode to %dx%dx%d failed\n%s",
                              w, h, bpp, D3DAppErrorToString(LastError));
        return FALSE;
    }
    return TRUE;
}

/*
 * D3DAppIRestoreDispMode
 * Restores the display mode to the current windows display mode.  The
 * bIgnoreWM_SIZE message is set because the display change generates a
 * WM_SIZE message which we don't want to resize the buffers on.
 */
BOOL
D3DAppIRestoreDispMode(void)
{
    bIgnoreWM_SIZE = TRUE;
    LastError = d3dappi.lpDD->lpVtbl->RestoreDisplayMode(d3dappi.lpDD);
    if (LastError != DD_OK) {
        D3DAppISetErrorString("RestoreDisplayMode failed.\n%s",
                              D3DAppErrorToString(LastError));
        return FALSE;
    }
    bIgnoreWM_SIZE = FALSE;
    return TRUE;
}

/*
 * D3DAppRememberWindowsMode
 * Record the current display mode in d3dappi.WindowsDisplay
 */
BOOL
D3DAppIRememberWindowsMode(void)
{
    DDSURFACEDESC ddsd;

    memset(&ddsd, 0, sizeof(DDSURFACEDESC));
    ddsd.dwSize = sizeof(DDSURFACEDESC);
    LastError = d3dappi.lpDD->lpVtbl->GetDisplayMode(d3dappi.lpDD, &ddsd);
    if (LastError != DD_OK) {
        D3DAppISetErrorString("Getting the current display mode failed.\n%s",
                              D3DAppErrorToString(LastError));
        return FALSE;
    }
    d3dappi.WindowsDisplay.w = ddsd.dwWidth;
    d3dappi.WindowsDisplay.h = ddsd.dwHeight;
    d3dappi.WindowsDisplay.bpp = ddsd.ddpfPixelFormat.dwRGBBitCount;
    return TRUE;
}

/***************************************************************************/
/*                          Misc DD Utilities                              */
/***************************************************************************/

/*
 * D3DAppIClearBuffers
 * Clear the front and back buffers to black
 */
BOOL
D3DAppIClearBuffers(void)
{
    DDSURFACEDESC ddsd;
    RECT dst;
    DDBLTFX ddbltfx;
    /*
     * Find the width and height of the front buffer by getting its
     * DDSURFACEDESC
     */
    if (d3dappi.lpFrontBuffer) {
        LastError = D3DAppIGetSurfDesc(&ddsd, d3dappi.lpFrontBuffer);
        if (LastError != DD_OK) {
            D3DAppISetErrorString("Failure getting the surface description of the front buffer before clearing.\n%s",
                                  D3DAppErrorToString(LastError));
            return FALSE;
        }
        /*
         * Clear the front buffer to black
         */
        memset(&ddbltfx, 0, sizeof(ddbltfx));
        ddbltfx.dwSize = sizeof(DDBLTFX);
        SetRect(&dst, 0, 0, ddsd.dwWidth, ddsd.dwHeight);
        LastError = d3dappi.lpFrontBuffer->lpVtbl->Blt(d3dappi.lpFrontBuffer,
                                                    &dst, NULL, NULL, 
                                                    DDBLT_COLORFILL | DDBLT_WAIT,
                                                    &ddbltfx);
        if (LastError != DD_OK) {
            D3DAppISetErrorString("Clearing the front buffer failed.\n%s",
                                  D3DAppErrorToString(LastError));
            return FALSE;
        }
    }
    if (d3dappi.lpBackBuffer) {
        /*
         * Find the width and height of the back buffer by getting its
         * DDSURFACEDESC
         */
        LastError = D3DAppIGetSurfDesc(&ddsd, d3dappi.lpBackBuffer);
        if (LastError != DD_OK) {
            D3DAppISetErrorString("Failure while getting the surface description of the back buffer before clearing.\n%s",
                                  D3DAppErrorToString(LastError));
            return FALSE;
        }
        /*
         * Clear the back buffer to black
         */
        memset(&ddbltfx, 0, sizeof(ddbltfx));
        ddbltfx.dwSize = sizeof(DDBLTFX);
        SetRect(&dst, 0, 0, ddsd.dwWidth, ddsd.dwHeight);
        LastError = d3dappi.lpBackBuffer->lpVtbl->Blt(d3dappi.lpBackBuffer, &dst,
                                                     NULL, NULL,
                                                     DDBLT_COLORFILL | DDBLT_WAIT,
                                                     &ddbltfx);
        if (LastError != DD_OK) {
            D3DAppISetErrorString("Clearing the front buffer failed.\n%s",
                                  D3DAppErrorToString(LastError));
            return FALSE;
        }
    }
    return TRUE;
}

/*
 * D3DAppIBPPToDDBD
 * Convert an integer bit per pixel number to a DirectDraw bit depth flag
 */
DWORD
D3DAppIBPPToDDBD(int bpp)
{
    switch(bpp) {
        case 1:
            return DDBD_1;
        case 2:
            return DDBD_2;
        case 4:
            return DDBD_4;
        case 8:
            return DDBD_8;
        case 16:
            return DDBD_16;
        case 24:
            return DDBD_24;
        case 32:
            return DDBD_32;
        default:
            return (DWORD)D3DAPP_BOGUS;
    }
}

/*
 * D3DAppTotalVideoMemory
 * Returns the amount of total video memory supported (not free)
 */
DWORD
D3DAppTotalVideoMemory(void)
{
    DDCAPS DriverCaps, HELCaps;
    memset (&DriverCaps, 0, sizeof(DDCAPS));
    DriverCaps.dwSize = sizeof(DDCAPS);
    memset (&HELCaps, 0, sizeof(DDCAPS));
    HELCaps.dwSize = sizeof(DDCAPS);
    LastError = d3dappi.lpDD->lpVtbl->GetCaps(d3dappi.lpDD, &DriverCaps,
                                              &HELCaps);
    if (LastError != DD_OK) {
        D3DAppISetErrorString("Getting DD capabilities failed while checking total video memory.\n%s",
                              D3DAppErrorToString(LastError));
        return 0L;
    }
    if (DriverCaps.dwVidMemTotal)
        return DriverCaps.dwVidMemTotal;
    else
        return HELCaps.dwVidMemTotal;
}
